//===----------------------------------------------------------------------===//
//
//                         BusTub
//
// buffer_pool_manager.cpp
//
// Identification: src/buffer/buffer_pool_manager.cpp
//
// Copyright (c) 2015-2019, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include "buffer/buffer_pool_manager.h"
#include <algorithm>
#include <mutex>
#include <list>
#include <unordered_map>

namespace bustub {

BufferPoolManager::BufferPoolManager(size_t pool_size, DiskManager *disk_manager, LogManager *log_manager)
    : pool_size_(pool_size), disk_manager_(disk_manager), log_manager_(log_manager) {
  // We allocate a consecutive memory space for the buffer pool.
  pages_ = new Page[pool_size_];
  replacer_ = new LRUReplacer(pool_size);
  // Initially, every page is in the free list.
  for (size_t i = 0; i < pool_size_; ++i) {
    free_list_.emplace_back(static_cast<int>(i));
  }
}

BufferPoolManager::~BufferPoolManager() {
  delete[] pages_;
  delete replacer_;
}

Page *BufferPoolManager::FetchPageImpl(page_id_t page_id) {
    std::scoped_lock<std::mutex> lock(latch_);
    auto target = page_table_.find(page_id);
    if(target !=page_table_.end()){
      frame_id_t frame_id = target->second;
      Page *p = pages_ +frame_id;
      p->pin_count++;
      replacer_->Pin(frame_id);
      return p;
    }

    frame_id_t frame_id =-1;
    Page *p = nullptr;
    if(!free_list_.empty()){
     frame_id = free_list_.back();
     free_list_.pop_back();
     assert(frame_id >=0 && frame_id <static_cast<int>(pool_size_));
     p = pages_ + frame_id;
    } else {
      bool victimized = replacer_->Victim(&frame_id);
      if(!victimized){
        return nullptr;
      }
      assert(frame_id>=0 && frame_id <static_cast<int>(pool_size_));
      p = pages_ + frame_id;

      if(p->IsDirty()){
        disk_manager_->WritePage(p->GetPageId(),p->GetData());
        p->is_dirty = false;
      }
      p->pin_count_ =0;
    }
    
    page_table_.erase(p->GetPageId());
    page_table_[page_id] = frame_id;

    p->page_id = page_id;
    p->ResetMemory();
    disk_manager->ReadPage(page_id,p->GetData());
    p->pin_count_++;
    return p;
  // 1.     Search the page table for the requested page (P).
  // 1.1    If P exists, pin it and return it immediately.
  // 1.2    If P does not exist, find a replacement page (R) from either the free list or the replacer.
  //        Note that pages are always found from the free list first.
  // 2.     If R is dirty, write it back to the disk.
  // 3.     Delete R from the page table and insert P.
  // 4.     Update P's metadata, read in the page content from disk, and then return a pointer to P.
  return nullptr;
}

bool BufferPoolManager::UnpinPageImpl(page_id_t page_id, bool is_dirty) {
  std::scoped_lock(latch_);
  auto target = page_table_.find(page_id);
  if(target==page_table_.end()){
    return false;
  }
  frame_id_t unpin = target->second;
  Page* unpin_page = pages_ + unpin;
  if(is_dirty){
    unpin_page->is_dirty_=true;
  }

  if(unpin_page->pin_count_==0){
    return false;
  }
  unpin_page->pin_count_--;
  if(unpin_page->GetPinCount()==0){
  replacer_->Unpin(unpin);
  }
  return true;
}

bool BufferPoolManager::FlushPageImpl(page_id_t page_id) {
  std::scoped_lock(latch_)
  auto target = page_table_.find(page_id);
  if(target!=page_table_.end()||page_id==INVALID_PAGE_ID){
    return false;
  }
  frame_id_t frame = target->second;
  disk_manager_=>WritePage(page_id,page_[frame].data_);
  // Make sure you call DiskManager::WritePage!
  return true;
}

Page *BufferPoolManager::NewPageImpl(page_id_t *page_id) {
      std::scoped_lock<std::mutex> lock(latch_);
      for(auto& pg:page_){
        if(page_table_.find(pg->page_id)==page_table_.end())
          return false;
      }
      frame_id_t frame_id =-1;
    

      

  // 0.   Make sure you call DiskManager::AllocatePage!
  // 1.   If all the pages in the buffer pool are pinned, return nullptr.
  // 2.   Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
  // 3.   Update P's metadata, zero out memory and add P to the page table.
  // 4.   Set the page ID output parameter. Return a pointer to P.
  return nullptr;
}

bool BufferPoolManager::DeletePageImpl(page_id_t page_id) {
  std::scoped_lock<std::lock>(latch_);
  disk_manager_->DeallocatePage(page_id);
  auto target = page_table_.find(page_id);
  if(target==page_table_::end()){
    return true;
  }
  frame_id_t frame_id = target->second;
  Page* p = page_ + frame_id; 
  if(p->pin_count_==0){
    if(p->is_dirty_){
      BufferPoolManager::FlushPageImpl(page_id);
    }
    page_table_.erase(page_id);
    p->pin_count_=0;
    p->page_id=INVALID_PAGE_ID;
    free_list_.push_back(frame_id);
    return true;
  }
  // 0.   Make sure you call DiskManager::DeallocatePage!
  // 1.   Search the page table for the requested page (P).
  // 1.   If P does not exist, return true.
  // 2.   If P exists, but has a non-zero pin-count, return false. Someone is using the page.
  // 3.   Otherwise, P can be deleted. Remove P from the page table, reset its metadata and return it to the free list.
  return false;
}

void BufferPoolManager::FlushAllPagesImpl() {
  for(const auto &p:page_table_){
  page_id_t page_id = p.first;
  BufferPoolManager::FlushPageImpl(page_id);
  }
  // You can do it!
}

}  // namespace bustub
